#include "MorphoCard.h"

BYTE                fc = 0x7C;              /// filled character, not used character
//BYTE                sz = 0x50;            /// size of register into morpho card (data to read/write), 0x50 (max)
const size_t        maxSzData   = 80;
const size_t        szWriteIns  = 5;        /// size of write apdu array
size_t              szReadIns   = 6;        /// size of read apdu array
const size_t        szRespIns   = 5;        /// size of response apdu array

size_t              len1RegData = 32 + 1;   /// length of data at one register
size_t              len2RegData = 1 + 1;    /// length of data at two register
size_t              len3RegData = 16 + 1;   /// length of data at three register
size_t              lenRegData  = -1;

/// write apdu
BYTE    writeApdu[szWriteIns + maxSzData];
//BYTE                rd = 0x21;          /// size to read at readApdu and responseApdu (32 + 1) (data + filled char)
/// select app from Morpho card
BYTE                selAppApdu[]        = { 0x00, 0xA4, 0x04, 0x00, 0x07, fc, fc, fc, fc, fc, fc, fc, fc };
/// application id to select
BYTE                appId[]             = { 0xA0, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x00 };
/// response apdu
BYTE    responseApdu[szRespIns];

BYTE                recvBuffer[258];        /// Response from the card.
DWORD               recvLength;             /// Length of the response.

MorphoCard::MorphoCard()
{
    cout << GREEN << "MorphoCard()" << RESET << endl;
    long    rv = sCardEstablishContext();
    if ( rv != 0 )
        cout << RED << "failed MorphoCard()" << RESET << endl;
    bool    b = searchReaders();
    if ( !b )
        cout << RED << "No found readers" << RESET << endl;
    cout << RED << "----------------------------------------------" << RESET << endl;
}

MorphoCard::~MorphoCard()
{
    sCardFreeMemory();
    sCardReleaseContext();
    cout << BLUE << "----------------------------------------------" << RESET << endl;
}

string MorphoCard::leerSmartCard(int r)
{    
    if ( !cardInserted(r) )         return string(64, 'X'); /// It happens when two or more readers are connected
    if ( !selectAppId() )           return string(64, 'Y'); /// It happens when a different card of onpe card is inserted
    /// here, el app is selected.
    string  header  = "xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz";
    string  value   = "@";
    string  serial  = "ONPEVEP201300001";
    recvLength = sizeof(recvBuffer);
    BYTE    readApdu[szReadIns];
    long rv = -1;
    /// register one
    genReadApdu(1, readApdu);
    rv = sCardTransmit(readApdu, sizeof(readApdu), recvBuffer, recvLength);
    if (rv != 0)                    return "";
    genResponseApdu(responseApdu);
    if ( !getResponseApdu(header) ) return "";
    /// register two
    genReadApdu(2, readApdu);
    rv = sCardTransmit(readApdu, sizeof(readApdu), recvBuffer, recvLength);
    if (rv != 0)                    return "";
    genResponseApdu(responseApdu);
    if ( !getResponseApdu(value) )  return "";
//    cout << "value = " << value << endl;
    header[18] = value[0];
    /// register three
    genReadApdu(3, readApdu);
    rv = sCardTransmit(readApdu, sizeof(readApdu), recvBuffer, recvLength);
    if (rv != 0)                    return "";
    genResponseApdu(responseApdu);
    if ( !getResponseApdu(serial) ) return "";
//    cout << "serial = " << serial << endl;
    rv = sCardDisconnect();
    if (rv != 0)                    cout << "failed sCardDisconnect()" << endl;
    header += "0000000000000000" + serial;
    cout << CYAN << "----------------------------------------------" << RESET << endl;
    return header;
}

bool MorphoCard::setData(string data, int r, int rg)
{
    size_t  sz_data = data.size();  /// size of data has to be 32
    string  value   = data.substr(18, 1);
    data[18] = '*';
    cout << "data = " << data << " --- " << sz_data << endl;

    genWriteApdu(rg, writeApdu);
    for (size_t i = 0; i < sz_data; i++)
        writeApdu[szWriteIns + i] = (BYTE)data[i]; /// five positions by instruction
    hexPrintFromByteArray(szWriteIns, writeApdu, (szWriteIns + sz_data + 1));
    /// Here, the write apdu array is ready

    /// transactions
    if (!cardInserted(r))    return false;
    if (!selectAppId())     return false;
    recvLength = sizeof(recvBuffer);

    long rv = sCardTransmit(writeApdu, (szWriteIns + sz_data + 1), recvBuffer, recvLength);
    if (rv != 0)        return false;

    string response = "";
    if ( !getResponseApdu(response) )    cout << "failed response after to write operation" << endl;

    if (rg != 1)
    {
        rv = sCardDisconnect();
        if (rv != 0)                    cout << "failed sCardDisconnect()" << endl;
        return true;                                /// size of data is 32
    }

    sz_data = value.size();

    genWriteApdu(2, writeApdu);                     /// point to register two
    for (size_t i = 0; i < sz_data; i++)
        writeApdu[szWriteIns + i] = (BYTE)value[i]; /// five positions by instruction
    hexPrintFromByteArray(szWriteIns, writeApdu, (szWriteIns + sz_data + 1));

    rv = sCardTransmit(writeApdu, (szWriteIns + sz_data + 1), recvBuffer, recvLength);
    if (rv != 0)        return false;

    response = "";
    if ( !getResponseApdu(response) )    cout << "failed response after to second write operation" << endl;

    rv = sCardDisconnect();
    if (rv != 0)                    cout << "failed sCardDisconnect()" << endl;

    return true;
}

bool    MorphoCard::setValor(char valor, int r)
{
    string value = "";
    value += valor;
    size_t sz_data = value.size();
//    cout << "value = " << value << endl;
    if (sz_data != 1)   return false;

    /// transactions
    if (!cardInserted(r))    return false;
    if (!selectAppId())     return false;
    recvLength = sizeof(recvBuffer);                /// absence of this line throws an error

    genWriteApdu(2, writeApdu);                     /// point to register two
    for (size_t i = 0; i < sz_data; i++)
        writeApdu[szWriteIns + i] = (BYTE)value[i]; /// five positions by instruction
    hexPrintFromByteArray(szWriteIns, writeApdu, (szWriteIns + sz_data + 1));

    long rv = sCardTransmit(writeApdu, (szWriteIns + sz_data + 1), recvBuffer, recvLength);
    if (rv != 0)        return false;

    string response = "";
    if ( !getResponseApdu(response) )    cout << "failed response to write operation at register two" << endl;

    rv = sCardDisconnect();
    if (rv != 0)                    cout << "failed sCardDisconnect()" << endl;
    cout << GREEN << "----------------------------------------------" << RESET << endl;
    return true;
}

bool MorphoCard::clean()
{
    return false;
}

bool    MorphoCard::cardInserted(int r)
{
    if ( readers.empty() )  return false;
    for (size_t i = 0; i < readers.size(); i++)
        cout << "readers[" << i << "]=" << readers[i] << endl;
    if ( sCardConnect(readers[r].c_str()) == 0) /// first found reader is by default reader
        return true;
    else
        cout << RED << "No card inserted"<< RESET << endl;
    return false;
}

bool    MorphoCard::connectCardToSelectedReader(string reader)
{
    bool retorna = false;
//    readers = getReaders();
    vector<string>::iterator it;
    it = find(readers.begin(), readers.end(), reader);
    if (it != readers.end())
    {
        cout << GREEN << "found reader = " << *it << RESET << endl;
        if ( sCardConnect(reader.c_str()) == 0 )    retorna = true;
    }
    else
    {
        cout << RED << "No found reader"<< RESET << endl;
    }
//    cout << RED << "----------------------------------------------" << RESET << endl;
    return retorna;
}

vector<string> MorphoCard::getReaders()
{
    return readers;
}

bool    MorphoCard::searchReaders()
{
    LPTSTR  mszReaders;
    LONG    sd = sCardListReaders(mszReaders);
    if (sd != 0)    return false;
    LPTSTR  Current;          /// Pointer to the current reader detected
    for (Current = mszReaders; *Current; Current += strlen(Current) + 1)
        readers.push_back(string(Current));
//    cout << RED << "----------------------------------------------" << RESET << endl;
    return !readers.empty();
}

bool    MorphoCard::getResponseApdu(string &sHeader)
{
    genResponseApdu(responseApdu);
    recvLength = sizeof(recvBuffer);
    long rv = sCardTransmit(responseApdu, sizeof(responseApdu), recvBuffer, recvLength);
    if (rv != 0)    return false;
    charPrintOnly(recvBuffer, fc);
    /// save data to string var
    stringstream ss;
    for(int i = 0; recvBuffer[i] != fc; i++)    ss << recvBuffer[i];
    sHeader = ss.str();
    return true;
}

bool    MorphoCard::selectAppId()
{
    bool retorna = false;
    /// fill selAppApdu with AppId
    size_t i;
    for (i = 0; i < sizeof(appId); i++)
        selAppApdu[i+5] = appId[i];

    recvLength = sizeof(recvBuffer);
    long rv = sCardTransmit(selAppApdu, sizeof(selAppApdu), recvBuffer, recvLength);
    if (rv == 0) retorna = true;
    cout << YELLOW << "----------------------------------------------" << RESET << endl;
    return retorna;
}

size_t  MorphoCard::lengthData(int regNum)
{
    if      (regNum == 1)   return  len1RegData;
    else if (regNum == 2)   return  len2RegData;
    else if (regNum == 3)   return  len3RegData;
    else                    return  maxSzData;
}

void    MorphoCard::genWriteApdu(int regNum, unsigned char *apdu)
{
    // begin write instruction
    lenRegData  = lengthData(regNum);
    apdu[0] = 0x00;
    apdu[1] = 0xD2;
    apdu[2] = 0x01;
    apdu[3] = (BYTE)regNum;
    apdu[4] = (BYTE)lenRegData;
    // end write instruction, begin data with filled char
    for (size_t i = 0; i < maxSzData; i++)
        apdu[i+5] = fc;
}

void    MorphoCard::genReadApdu(int regNum, unsigned char *apdu)
{
//    BYTE                readApdu[]          = { 0x00, 0xB2, 0x01, 0x01, 0x00, /*sz*/rd };
    lenRegData  = lengthData(regNum);
    apdu[0] = 0x00;
    apdu[1] = 0xB2;
    apdu[2] = 0x01;
    apdu[3] = (BYTE)regNum;
    apdu[4] = 0x00;
    apdu[5] = (BYTE)lenRegData;
}

void    MorphoCard::genResponseApdu(unsigned char *apdu)
{
//BYTE                responseApdu[]      = { 0x00, 0xC0, 0x00, 0x00, /*sz*/rd };
    apdu[0] = 0x00;
    apdu[1] = 0xC0;
    apdu[2] = 0x00;
    apdu[3] = 0x00;
    apdu[4] = (BYTE)lenRegData;
//    cout << "lenRegData = " << lenRegData << endl;
}
